<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>函数图像分析工具（含切线）</title>
    <script src="d3.v7.min.js"></script>
    <script src="function-plot.js"></script>
    <script src="math.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
            min-height: 100vh;
            padding: 20px;
            color: #fff;
        }
        
        .container {
            max-width: 1400px;
            margin: 0 auto;
            padding: 20px;
        }
        
        header {
            text-align: center;
            margin-bottom: 30px;
            padding: 30px;
            background: rgba(25, 35, 70, 0.7);
            border-radius: 15px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(100, 149, 237, 0.3);
        }
        
        h1 {
            font-size: 2.8rem;
            margin-bottom: 15px;
            background: linear-gradient(45deg, #4fc3f7, #64ffda, #2196F3);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
        }
        
        .subtitle {
            font-size: 1.2rem;
            opacity: 0.9;
            max-width: 800px;
            margin: 0 auto;
            line-height: 1.6;
            color: #e0f7fa;
        }
        
        .main-content {
            display: flex;
            flex-wrap: wrap;
            gap: 25px;
            margin-bottom: 30px;
        }
        
        .control-panel {
            flex: 1;
            min-width: 320px;
            background: rgba(25, 35, 70, 0.7);
            padding: 25px;
            border-radius: 15px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(100, 149, 237, 0.3);
        }
        
        .plot-container {
            flex: 2;
            min-width: 400px;
            background: rgba(25, 35, 70, 0.7);
            padding: 20px;
            border-radius: 15px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(100, 149, 237, 0.3);
        }
        
        #plot {
            width: 100%;
            height: 500px;
            background: #ffffff;
            border-radius: 8px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
        }
        
        .analysis-panel {
            background: rgba(25, 35, 70, 0.7);
            padding: 25px;
            border-radius: 15px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
            backdrop-filter: blur(10px);
            margin-top: 20px;
            border: 1px solid rgba(100, 149, 237, 0.3);
        }
        
        .panel-title {
            display: flex;
            align-items: center;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 2px solid #4fc3f7;
        }
        
        .panel-title h2 {
            font-size: 1.6rem;
            color: #4fc3f7;
            margin-right: 15px;
        }
        
        .panel-title .icon {
            background: #4fc3f7;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.2rem;
        }
        
        .control-group {
            margin-bottom: 25px;
        }
        
        h3 {
            font-size: 1.3rem;
            margin-bottom: 15px;
            color: #64ffda;
        }
        
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
            color: #e0f7fa;
        }
        
        input, select, button {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: none;
            border-radius: 8px;
            background: rgba(255, 255, 255, 0.1);
            color: white;
            font-size: 16px;
            border: 1px solid rgba(255, 255, 255, 0.2);
        }
        
        input:focus, select:focus {
            outline: none;
            border-color: #4fc3f7;
            background: rgba(255, 255, 255, 0.15);
            box-shadow: 0 0 0 2px rgba(79, 195, 247, 0.3);
        }
        
        button {
            background: linear-gradient(45deg, #2196F3, #00bcd4);
            color: white;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s ease;
            border: none;
            margin-top: 10px;
            font-size: 1.1rem;
            padding: 14px;
        }
        
        button:hover {
            transform: translateY(-3px);
            box-shadow: 0 5px 15px rgba(33, 150, 243, 0.5);
        }
        
        .preset-buttons {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
        }
        
        .preset-btn {
            background: rgba(255, 255, 255, 0.1);
            padding: 12px;
            text-align: center;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
            font-size: 14px;
            border: 1px solid rgba(100, 149, 237, 0.3);
        }
        
        .preset-btn:hover {
            background: rgba(79, 195, 247, 0.3);
            transform: translateY(-2px);
        }
        
        .analysis-content {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 25px;
        }
        
        .analysis-card {
            background: rgba(40, 50, 90, 0.5);
            padding: 20px;
            border-radius: 10px;
            border: 1px solid rgba(100, 149, 237, 0.2);
        }
        
        .analysis-card h4 {
            font-size: 1.2rem;
            color: #4fc3f7;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
        }
        
        .analysis-card h4 .icon {
            background: rgba(79, 195, 247, 0.2);
            width: 30px;
            height: 30px;
            border-radius: 50%;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            margin-right: 10px;
            font-size: 0.9rem;
        }
        
        .analysis-card p {
            font-size: 1.1rem;
            line-height: 1.6;
            color: #f0f0ff;
            background: rgba(0, 0, 0, 0.2);
            padding: 15px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            min-height: 100px;
        }
        
        .function-info {
            display: flex;
            align-items: center;
            margin-bottom: 15px;
            padding: 10px;
            background: rgba(40, 50, 90, 0.5);
            border-radius: 8px;
            justify-content: space-between;
        }
        
        .legend-item {
            display: flex;
            align-items: center;
            margin-right: 20px;
        }
        
        .legend-item .color-box {
            width: 20px;
            height: 20px;
            border-radius: 4px;
            margin-right: 10px;
        }
        
        .legend-item .fn-label {
            font-size: 1rem;
            font-weight: 500;
            font-family: 'Courier New', monospace;
        }
        
        .tangent-controls {
            display: grid;
            grid-template-columns: 1fr auto;
            gap: 10px;
            align-items: end;
        }
        
        .tangent-input-group {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
        }
        
        .tangent-btn {
            background: linear-gradient(45deg, #ff9800, #ff5722);
            padding: 12px 15px;
            border-radius: 8px;
            font-weight: bold;
        }
        
        footer {
            text-align: center;
            margin-top: 40px;
            padding: 20px;
            font-size: 0.9rem;
            opacity: 0.8;
            color: #a0a0d0;
        }
        
        .function-input-group {
            display: grid;
            grid-template-columns: 1fr auto;
            gap: 10px;
            align-items: center;
        }
        
        @media (max-width: 768px) {
            .main-content {
                flex-direction: column;
            }
            
            .plot-container {
                min-width: 100%;
            }
            
            header {
                padding: 20px 15px;
            }
            
            h1 {
                font-size: 2.2rem;
            }
            
            .function-info {
                flex-direction: column;
                align-items: flex-start;
            }
            
            .legend-item {
                margin-bottom: 10px;
            }
            
            .tangent-controls {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>函数图像分析工具</h1>
            <p class="subtitle">基于 function-plot.js 的数学函数可视化分析工具，支持绘制切线、弧度制坐标显示，并提供函数的单调区间、值域和定义域分析</p>
        </header>
        
        <div class="main-content">
            <div class="control-panel">
                <div class="control-group">
                    <h3>函数设置</h3>
                    <div class="function-input-group">
                        <div>
                            <label for="functionInput">函数表达式 (使用 'x' 作为变量):</label>
                            <input type="text" id="functionInput" value="sin(x)" placeholder="例如: sin(x), x^2, log(x)">
                        </div>
                        <button id="plotButton" >绘制</button>
                    </div>
                    
                    <label for="colorPicker">曲线颜色:</label>
                    <input type="color" id="colorPicker" value="#2196F3">
                    
                    <label for="widthRange">曲线宽度:</label>
                    <input type="range" id="widthRange" min="1" max="10" value="3">
                </div>
                
                <div class="control-group">
                    <h3>切线设置</h3>
                    <div class="tangent-controls">
                        <div>
                            <label for="tangentPoint">切点横坐标 (x₀):</label>
                            <div class="tangent-input-group">
                                <input type="number" id="tangentPoint" value="1.0" step="0.1" placeholder="输入切点坐标">
                                <button id="tangentButton" class="tangent-btn">添加切线</button>
                            </div>
                        </div>
                    </div>
                    
                    <div id="tangentList" style="margin-top: 15px;">
                        <!-- 切线列表将由JS动态生成 -->
                    </div>
                </div>
                
                <div class="control-group">
                    <h3>坐标轴设置 (弧度制)</h3>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
                        <div>
                            <label for="xMin">X 最小值:</label>
                            <input type="number" id="xMin" value="-6.28">
                        </div>
                        <div>
                            <label for="xMax">X 最大值:</label>
                            <input type="number" id="xMax" value="6.28">
                        </div>
                    </div>
                    
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 15px;">
                        <div>
                            <label for="yMin">Y 最小值:</label>
                            <input type="number" id="yMin" value="-1.5">
                        </div>
                        <div>
                            <label for="yMax">Y 最大值:</label>
                            <input type="number" id="yMax" value="1.5">
                        </div>
                    </div>
                </div>
                
                <div class="control-group">
                    <h3>预设函数</h3>
                    <div class="preset-buttons">
                        <div class="preset-btn" data-fn="sin(x)">正弦函数</div>
                        <div class="preset-btn" data-fn="cos(x)">余弦函数</div>
                        <div class="preset-btn" data-fn="x^2">二次函数</div>
                        <div class="preset-btn" data-fn="x^3 - 2*x">三次函数</div>
                        <div class="preset-btn" data-fn="exp(x)">指数函数</div>
                        <div class="preset-btn" data-fn="log(x)">对数函数</div>
                    </div>
                </div>
                
                <button id="resetButton">重置设置</button>
            </div>
            
            <div class="plot-container">
                <div class="panel-title">
                    <div class="icon">📈</div>
                    <h2>函数图像分析</h2>
                </div>
                
                <div class="function-info">
                    <div class="legend">
                        <div class="legend-item">
                            <div class="color-box" id="functionColorBox" style="background-color: #2196F3;"></div>
                            <div class="fn-label" id="currentFunction">f(x) = sin(x)</div>
                        </div>
                        <div class="legend-item">
                            <div class="color-box" style="background-color: #FF5722;"></div>
                            <div class="fn-label">切线</div>
                        </div>
                        <div class="legend-item">
                            <div class="color-box" style="background-color: #FFEB3B; border-radius: 50%;"></div>
                            <div class="fn-label">切点</div>
                        </div>
                    </div>
                </div>
                
                <div id="plot"></div>
            </div>
        </div>
        
        <div class="analysis-panel">
            <div class="panel-title">
                <div class="icon">📊</div>
                <h2>函数性质分析</h2>
            </div>
            
            <div class="analysis-content">
                <div class="analysis-card">
                    <h4><div class="icon">D</div>定义域分析</h4>
                    <p id="domainAnalysis">函数 sin(x) 的定义域为所有实数，即 (-∞, +∞)</p>
                </div>
                
                <div class="analysis-card">
                    <h4><div class="icon">R</div>值域分析</h4>
                    <p id="rangeAnalysis">函数 sin(x) 的值域为 [-1, 1]</p>
                </div>
                
                <div class="analysis-card">
                    <h4><div class="icon">↗</div>单调区间分析</h4>
                    <p id="monotonicAnalysis">在区间 [-π/2 + 2kπ, π/2 + 2kπ] 上单调递增<br>在区间 [π/2 + 2kπ, 3π/2 + 2kπ] 上单调递减 (k为整数)</p>
                </div>
                
                <div class="analysis-card">
                    <h4><div class="icon">↘</div>切线信息</h4>
                    <p id="tangentInfo">在 x₀ = 1.0 处，斜率为 cos(1.0) ≈ 0.540</p>
                </div>
            </div>
        </div>
        
        <footer>
            <p>© 2023 函数图像分析工具 | 基于 function-plot.js 和 math.js 库开发 | 使用弧度制坐标</p>
        </footer>
    </div>

    <script>
        // 存储切线信息
        let tangents = [];
        
        // 页面加载完成后初始化绘图
        document.addEventListener('DOMContentLoaded', function() {
            // 初始绘制
            plotFunction();
            
            // 绑定绘图按钮事件
            document.getElementById('plotButton').addEventListener('click', plotFunction);
            
            // 绑定切线按钮事件
            document.getElementById('tangentButton').addEventListener('click', addTangent);
            
            // 绑定重置按钮
            document.getElementById('resetButton').addEventListener('click', function() {
                document.getElementById('functionInput').value = 'sin(x)';
                document.getElementById('colorPicker').value = '#2196F3';
                document.getElementById('widthRange').value = '3';
                document.getElementById('xMin').value = '-6.28';
                document.getElementById('xMax').value = '6.28';
                document.getElementById('yMin').value = '-1.5';
                document.getElementById('yMax').value = '1.5';
                document.getElementById('tangentPoint').value = '1.0';
                
                // 清除所有切线
                tangents = [];
                updateTangentList();
                plotFunction();
            });
            
            // 绑定预设按钮事件
            const presetButtons = document.querySelectorAll('.preset-btn');
            presetButtons.forEach(button => {
                button.addEventListener('click', function() {
                    const fn = this.getAttribute('data-fn');
                    document.getElementById('functionInput').value = fn;
                    updateAnalysis(fn);
                    plotFunction();
                });
            });
            
            // 绑定输入框回车事件
            document.getElementById('functionInput').addEventListener('keyup', function(event) {
                if (event.key === 'Enter') {
                    plotFunction();
                }
            });
            
            // 绑定颜色变化更新
            document.getElementById('colorPicker').addEventListener('input', function() {
                document.getElementById('functionColorBox').style.backgroundColor = this.value;
            });
        });
        
        // 添加切线
        function addTangent() {
            const tangentPoint = parseFloat(document.getElementById('tangentPoint').value);
            if (isNaN(tangentPoint)) return;
            
            // 添加到切线列表
            tangents.push({
                point: tangentPoint,
                color: `hsl(${Math.random() * 360}, 70%, 60%)` // 随机颜色
            });
            
            updateTangentList();
            plotFunction();
        }
        
        // 更新切线列表显示
        function updateTangentList() {
            const tangentList = document.getElementById('tangentList');
            tangentList.innerHTML = '';
            
            tangents.forEach((tangent, index) => {
                const tangentElement = document.createElement('div');
                tangentElement.className = 'tangent-item';
                tangentElement.style.display = 'flex';
                tangentElement.style.justifyContent = 'space-between';
                tangentElement.style.alignItems = 'center';
                tangentElement.style.padding = '10px';
                tangentElement.style.background = 'rgba(255,255,255,0.05)';
                tangentElement.style.borderRadius = '8px';
                tangentElement.style.marginBottom = '8px';
                
                tangentElement.innerHTML = `
                    <div style="display:flex; align-items:center;">
                        <div style="width:12px; height:12px; background:${tangent.color}; margin-right:10px; border-radius:2px;"></div>
                        <span>在 x₀ = ${tangent.point.toFixed(2)}</span>
                    </div>
                    <button class="remove-tangent" data-index="${index}" style="background:#f44336; padding:5px 10px; border-radius:4px; border:none; color:white; cursor:pointer;">删除</button>
                `;
                
                tangentList.appendChild(tangentElement);
            });
            
            // 绑定删除按钮事件
            document.querySelectorAll('.remove-tangent').forEach(button => {
                button.addEventListener('click', function() {
                    const index = parseInt(this.getAttribute('data-index'));
                    tangents.splice(index, 1);
                    updateTangentList();
                    plotFunction();
                });
            });
        }
        
        // 更新函数分析
        function updateAnalysis(fn) {
            let domainAnalysis = "";
            let rangeAnalysis = "";
            let monotonicAnalysis = "";
            
            switch(fn) {
                case 'sin(x)':
                    domainAnalysis = "函数 sin(x) 的定义域为所有实数，即 (-∞, +∞)";
                    rangeAnalysis = "函数 sin(x) 的值域为 [-1, 1]";
                    monotonicAnalysis = "在区间 [-π/2 + 2kπ, π/2 + 2kπ] 上单调递增<br>在区间 [π/2 + 2kπ, 3π/2 + 2kπ] 上单调递减 (k为整数)";
                    break;
                case 'cos(x)':
                    domainAnalysis = "函数 cos(x) 的定义域为所有实数，即 (-∞, +∞)";
                    rangeAnalysis = "函数 cos(x) 的值域为 [-1, 1]";
                    monotonicAnalysis = "在区间 [2kπ, π + 2kπ] 上单调递减<br>在区间 [π + 2kπ, 2π + 2kπ] 上单调递增 (k为整数)";
                    break;
                case 'x^2':
                    domainAnalysis = "函数 x² 的定义域为所有实数，即 (-∞, +∞)";
                    rangeAnalysis = "函数 x² 的值域为 [0, +∞)";
                    monotonicAnalysis = "在区间 (-∞, 0] 上单调递减<br>在区间 [0, +∞) 上单调递增";
                    break;
                case 'x^3 - 2*x':
                    domainAnalysis = "函数 x³ - 2x 的定义域为所有实数，即 (-∞, +∞)";
                    rangeAnalysis = "函数 x³ - 2x 的值域为所有实数，即 (-∞, +∞)";
                    monotonicAnalysis = "在区间 (-∞, -√(2/3)) 上单调递增<br>在区间 (-√(2/3), √(2/3)) 上单调递减<br>在区间 (√(2/3), +∞) 上单调递增";
                    break;
                case 'log(x)':
                    domainAnalysis = "函数 log(x) 的定义域为 (0, +∞)";
                    rangeAnalysis = "函数 log(x) 的值域为 (-∞, +∞)";
                    monotonicAnalysis = "在整个定义域 (0, +∞) 上单调递增";
                    break;
                case 'exp(x)':
                    domainAnalysis = "函数 eˣ 的定义域为所有实数，即 (-∞, +∞)";
                    rangeAnalysis = "函数 eˣ 的值域为 (0, +∞)";
                    monotonicAnalysis = "在整个定义域 (-∞, +∞) 上单调递增";
                    break;
                default:
                    domainAnalysis = "定义域：函数定义有效的所有x值集合";
                    rangeAnalysis = "值域：函数所有可能的输出值集合";
                    monotonicAnalysis = "单调区间：函数递增或递减的区间";
            }
            
            document.getElementById('domainAnalysis').innerHTML = domainAnalysis;
            document.getElementById('rangeAnalysis').innerHTML = rangeAnalysis;
            document.getElementById('monotonicAnalysis').innerHTML = monotonicAnalysis;
            document.getElementById('currentFunction').textContent = `f(x) = ${fn}`;
        }
        
        // 计算导数（数值方法）
        function derivative(fn, x, h = 0.0001) {
            try {
                // 使用math.js解析函数
                const f = math.compile(fn);
                
                // 计算f(x + h)和f(x - h)
                const f_xh = f.evaluate({x: x + h});
                const f_xmh = f.evaluate({x: x - h});
                
                // 中心差分法计算导数
                return (f_xh - f_xmh) / (2 * h);
            } catch (e) {
                console.error("导数计算错误:", e);
                return 0;
            }
        }
        
        // 绘图函数
        function plotFunction() {
            // 获取用户输入
            const functionInput = document.getElementById('functionInput').value;
            const color = document.getElementById('colorPicker').value;
            const width = document.getElementById('widthRange').value;
            
            // 更新颜色指示框
            document.getElementById('functionColorBox').style.backgroundColor = color;
            
            // 更新函数标签
            document.getElementById('currentFunction').textContent = `f(x) = ${functionInput}`;
            
            // 更新函数分析
            updateAnalysis(functionInput);
            
            // 获取坐标轴范围
            const xMin = parseFloat(document.getElementById('xMin').value);
            const xMax = parseFloat(document.getElementById('xMax').value);
            const yMin = parseFloat(document.getElementById('yMin').value);
            const yMax = parseFloat(document.getElementById('yMax').value);
            
            // 准备绘图数据
            const plotData = [{
                fn: functionInput,
                title: `f(x) = ${functionInput}`,
                graphType: 'polyline',
                color: color,
                width: width
            }];
            
            // 添加切线
            let tangentInfo = "没有添加切线";
            
            tangents.forEach(tangent => {
                const x0 = tangent.point;
                
                try {
                    // 计算函数值
                    const f = math.compile(functionInput);
                    const y0 = f.evaluate({x: x0});
                    
                    // 计算导数
                    const slope = derivative(functionInput, x0);
                    
                    // 切线方程：y = slope*(x - x0) + y0
                    const tangentFn = `${slope.toFixed(4)}*(x - ${x0}) + ${y0}`;
                    
                    // 添加切线
                    plotData.push({
                        fn: tangentFn,
                        graphType: 'polyline',
                        color: tangent.color,
                        width: 2,
                        nSamples: 2 // 只需要两点确定一条直线
                    });
                    
                    // 添加切点
                    plotData.push({
                        points: [[x0, y0]],
                        fnType: 'points',
                        graphType: 'scatter',
                        color: '#FFEB3B',
                        radius: 6,
                        borderColor: '#FFC107',
                        borderWidth: 2
                    });
                    
                    // 更新切线信息
                    tangentInfo = `在 x₀ = ${x0.toFixed(2)} 处，斜率为 ${slope.toFixed(4)}`;
                } catch (e) {
                    console.error("切线绘制错误:", e);
                    tangentInfo = `在 x₀ = ${x0.toFixed(2)} 处计算切线失败`;
                }
            });
            
            document.getElementById('tangentInfo').textContent = tangentInfo;
            
            try {
                // 使用 function-plot 绘制函数
                functionPlot({
                    target: '#plot',
                    width: document.querySelector('#plot').clientWidth,
                    height: 450,
                    yAxis: { 
                        domain: [yMin, yMax],
                        label: 'y'
                    },
                    xAxis: { 
                        domain: [xMin, xMax],
                        label: 'x (弧度)'
                    },
                    grid: true,
                    tip: {
                        xLine: true,
                        yLine: true,
                        renderer: function(x, y, index) {
                            // 格式化显示为弧度值
                            return `x: ${x.toFixed(3)} rad\ny: ${y.toFixed(3)}`;
                        }
                    },
                    data: plotData
                });
            } catch (error) {
                // 处理错误
                alert('函数绘制错误: ' + error.message);
                console.error(error);
            }
        }
        
        // 添加窗口大小调整事件
        window.addEventListener('resize', function() {
            plotFunction();
        });
    </script>
</body>
</html>
    